{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.1 Tensorflow计算模型——计算图\n",
    "### 3.1.1 计算图的概念"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- Tensor：张量，表明数据结构，可以理解为多维数组；\n",
    "- Flow：‘流’，表明计算模型，直观表示张量之间通过计算相互转化的过程。\n",
    "\n",
    "**TF中，每个计算都是计算图中的一个节点，而节点之间的边描述了计算之间的依赖关系。**\n",
    "\n",
    "<p align='center'> \n",
    "   <img src=images/图3.1计算图基本模型.JPG>\n",
    "</p>"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.1.2 计算图的使用"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Tensorflow程序一般分为两个阶段：定义和执行。**\n",
    "\n",
    "通过a.graph可以查看张量所属的计算图，默认为tf.get_default_graph。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "a = tf.constant([1.0, 2.0], name='a')\n",
    "b = tf.constant([2.0, 3.0], name='b')\n",
    "result = a + b\n",
    "\n",
    "print(a.graph is tf.get_default_graph())"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "tf.Graph()函数生成新的计算图，不同计算图上的张量和运算不会共享。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0.]\n",
      "[1.]\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "g1 = tf.Graph()\n",
    "with g1.as_default():\n",
    "    v = tf.get_variable(\"v\", [1], initializer = tf.zeros_initializer()) # 设置初始值为0\n",
    "\n",
    "g2 = tf.Graph()\n",
    "with g2.as_default():\n",
    "    v = tf.get_variable(\"v\", [1], initializer = tf.ones_initializer())  # 设置初始值为1\n",
    "    \n",
    "with tf.Session(graph = g1) as sess:\n",
    "    tf.global_variables_initializer().run()\n",
    "    with tf.variable_scope(\"\", reuse=True):  # 这一行很关键，明确变量reuse\n",
    "        print(sess.run(tf.get_variable(\"v\")))\n",
    "\n",
    "with tf.Session(graph = g2) as sess:\n",
    "    tf.global_variables_initializer().run()\n",
    "    with tf.variable_scope(\"\", reuse=True):\n",
    "        print(sess.run(tf.get_variable(\"v\")))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**Tensorflow中的计算图不仅可以隔离张量和计算，还提供了管理张量和计算的机制**，可以通过tf.Graph.device函数来指定运行计算的设备。12章会介绍。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3. 5.]\n",
      "[3. 5.]\n"
     ]
    }
   ],
   "source": [
    "g = tf.Graph()\n",
    "\n",
    "with g.device('/cpu:0'):\n",
    "    result_2 = a + b\n",
    "    with tf.Session() as sess:\n",
    "        tf.global_variables_initializer().run()\n",
    "        print(sess.run(result_2))\n",
    "        \n",
    "with g.device('/gpu:0'):\n",
    "    result_3 = a + b\n",
    "    with tf.Session() as sess:\n",
    "        tf.global_variables_initializer().run()\n",
    "        print(sess.run(result_3))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "True\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "''"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(result.device == result_2.device == result_3.device)\n",
    "result.device\n",
    "# 存疑：上cell中指定为cpu/gpu为何均为True？\n",
    "# 猜测：只安装了tf-gpu版本？"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**有效地整理TF程序中的资源也是计算图地一个重要功能**，在一个计算图中可以以通过集合（collection）来管理不同类别的资源，资源包括张量、变量或运行TF程序所需要地队列资源等，tf.add_to_collection函数可以将资源加入一个或多个集合，tf.get_collection可以获取一个集合中的所有资源。TF自动管理了常用集合，如\n",
    "\n",
    "| 集合名称 | 集合内容 | 使用场景 |\n",
    "| :------: | :------: | :------: |\n",
    "| tf.GraphKeys.VARIABLES | 所有变量 | 持久化TF模型 |\n",
    "| tf.GraphKeys.TRAINABLE_VARIABLES | 可学习的变量（一般指神经网络中的参数） | 模型训练、生成模型可视化内容 |\n",
    "| tf.GraphKeys.SUMMARIES | 日志生成相关的变量 | TF可视化 |\n",
    "| tf.GraphKeys.QUEUE_RUNNERS | 处理输入的QueueRunners | 处理输入 |\n",
    "| tf.Graphkeys.MOVING_AVERAGE_VARIABLES | 所有计算了滑动平均值的变量 | 计算变量的滑动平均值 |"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## 3.2. Tensorflow数据类型——张量\n",
    "### 3.2.1 张量的概念\n",
    "TF中，所有数据都是通过张量形式表示的，功能角度相当于多维数组。\n",
    "- 零阶张量表示标量（Scalar），也就是一个数；\n",
    "- 一阶张量为向量（Vector），也就是一维数组；\n",
    "- n阶张量可以理解为n维数组。\n",
    "\n",
    "**但张量在TF中的实现并不是直接采用数组的形式，它只是对TF中运算结果的引用，并没有真正保存数字，保存的是如何得到这些数字的计算过程。**"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Tensor(\"add_3:0\", shape=(2,), dtype=float32)\n"
     ]
    }
   ],
   "source": [
    "import tensorflow as tf\n",
    "\n",
    "a = tf.constant([1.0, 2.0], name=\"a\")\n",
    "b = tf.constant([2.0, 3.0], name=\"b\")\n",
    "result = a + b\n",
    "\n",
    "print(result)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "可以看到，张量主要保存了三个属性：\n",
    "- **name，名字**，张量唯一标识符，同时给出了这个张量是如何计算出来的，‘node：src_output’形式，node为节点名，src_output表示当前张量来自节点的第几个输出，均是从0开始计数；\n",
    "- **shape，维度**，张量维度信息，如上(2,)表示result这个张量是一个一维数组，长度为2；\n",
    "- **dtype，类型**，每个张量都会有唯一的类型，TF会对参与运算的所有张量进行类型的检查，不匹配时报错，如上若a为[1, 2]，则会报ValueError，但此时如果a在定义时指定dtype=tf.float32则ok。TF默认不带小数点数字为tf.int32，带小数点为tf.float32，一般建议通过指定dtype来明确变量或常量类型。TF支持14种类型，包括实数（tf.float32, tf.float64）、整数（tf.int8, tf.int16, tf.int32, tf.int64, tf.uint8）、布尔型（tf.bool）和复数（tf.complex64, tf.complex128）。"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### 3.2.2 张量的使用\n",
    "较TF的计算模型简单，张量的使用分为两类：\n",
    "- **对中间结果结果的引用**，当计算包含很多中间结果时可以提高代码可读性，也方便某些情况下对中间结果的获取，如下例cell；\n",
    "- **计算图构造完之后，张量用来获取结果**，也就是得到的真实的数字，需要借助下一节的‘会话’来实现。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "# 使用张量记录中间结果\n",
    "a = tf.constant([1.0, 2.0], name=\"a\")\n",
    "b = tf.constant([2.0, 3.0], name=\"b\")\n",
    "result = a + b\n",
    "\n",
    "# 直接计算向量的和，可读性较差，且不便于后续对中间结果调用\n",
    "result = tf.constant([1.0, 2.0], name=\"a\") + \\\n",
    "         tf.constant([2.0, 3.0], name=\"b\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## 3.3 TensorFlow运行模型——会话\n",
    "**会话拥有并管理TF程序运行时的所有资源，所有计算完成后需要关闭会话来帮助系统回收资源，否则就可能出现资源泄露的问题**。TF中使用会话有两种方式："
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3. 5.]\n"
     ]
    }
   ],
   "source": [
    "# TF会话方式一：\n",
    "\n",
    "# 创建一个会话。\n",
    "sess = tf.Session()\n",
    "\n",
    "# 使用会话得到之前计算的结果，即上文中提到的张量的第二种作用\n",
    "print(sess.run(result))\n",
    "\n",
    "# 关闭会话使得本次运行中使用到的资源可以被释放。\n",
    "sess.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "- 上面这种方式需要明确调用Session.close()来关闭会话，当程序因为异常退出时，关闭会话的函数可能不会被执行而导致资源泄露。\n",
    "\n",
    "- 下面这种方式通过使用python上下文管理器的机制，既解决了因为异常退出时资源释放的问题，也解决了忘记调用Session.close()函数而产生的资源泄露。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3. 5.]\n"
     ]
    }
   ],
   "source": [
    "# TF会话方式二：\n",
    "\n",
    "# 上下文退出时，会话关闭和资源释放也自动完成了。\n",
    "with tf.Session() as sess:\n",
    "    print(sess.run(result))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**和TF会默认指定计算图不同，TF的会话不会自动生成默认的会话，需要手动指定**，指定后便可以通过tf.Tensor.eval函数来计算张量的值。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3. 5.]\n"
     ]
    }
   ],
   "source": [
    "sess = tf.Session()\n",
    "with sess.as_default():\n",
    "     print(result.eval())  # tf.Tensor.eval使用方式之一:通过先指定会话"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3. 5.]\n",
      "[3. 5.]\n"
     ]
    }
   ],
   "source": [
    "sess = tf.Session()\n",
    "\n",
    "# 下面的两个命令有相同的功能。\n",
    "print(sess.run(result))\n",
    "print(result.eval(session=sess))  # tf.Tensor.eval使用方式之二：将会话作为参数指定"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "另外，在交互式环境下（比如Python脚本或者Jupyter的编辑器下），通过设置默认会话的方式来获取张量的取值更加方便。所以TensorFlow 提供了一种在交互式环境下直接构建默认会话的函数。这个函数就是tf.lnteractiveSession 。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[3. 5.]\n"
     ]
    }
   ],
   "source": [
    "sess = tf.InteractiveSession ()\n",
    "print(result.eval())  # tf.Tensor.eval使用方式之三：交互环境下创建tf.lnteractiveSession型会话\n",
    "sess.close()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "最后，无论使用哪种方式都通过ConfigProto Protocol Buffer来配置会话，可以配置*并行的线程数、GPU 分配策略、运算超时时间*等参数。"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "metadata": {},
   "outputs": [],
   "source": [
    "config = tf.ConfigProto(allow_soft_placement=True, log_device_placement=True)\n",
    "sess1 = tf.InteractiveSession(config=config)\n",
    "sess2 = tf.Session(config=config)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "在这些参数中，最常使用的有两个。\n",
    "\n",
    "第一个是**allow_soft_placement** ，这是一个布尔型的参数，当它为True 时， 在以下任意一个条件成立时， GPU 上的运算可以放到CPU 上进行:\n",
    "1. 运算无法在GPU 上执行。\n",
    "2. 没有GPU 资源（比如运算被指定在第二个GPU 上运行，但是机器只有一个GPU ） 。\n",
    "3. 运算输入包含对CPU 计算结果的引用。\n",
    "\n",
    "这个参数的默认值为False ，但是为了使得代码的可移植性更强，在有GPU 的环境下这个参数一般会被设置为True 。不同的GPU 驱动版本可能对计算的支持有略微的区别，通过将allow_soft _placement 参数设为True ， 当某些运算无法被当前GPU 支持时，可以自动调整到CPU 上，而不是报错。类似地，通过将这个参数设置为True ，可以让程序在拥有不同数量的GPU 机器上顺利运行。\n",
    "\n",
    "第二个是**log_device placement**， 这也是一个布尔型的参数，当它为True 时日志中将会记录每个节点被安排在哪个设备上以方便调试。而在生产环境中将这个参数设置为False 可以减少日志量。"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
